Conversation
maxn990
left a comment
There was a problem hiding this comment.
looks really good over all! i left a few comments, but love the general approach :he-is-brighter:
apps/frontend/src/app.tsx
Outdated
| path: '/landing-page', | ||
| element: ( | ||
| <Authenticator components={components}> | ||
| <LandingPage /> | ||
| </Authenticator> | ||
| ), | ||
| }, | ||
| { | ||
| path: '/pantry-overview', | ||
| element: ( | ||
| <Authenticator components={components}> | ||
| <PantryOverview /> | ||
| </Authenticator> | ||
| ), | ||
| }, | ||
| { | ||
| path: '/pantry-dashboard/:pantryId', | ||
| element: ( | ||
| <Authenticator components={components}> |
There was a problem hiding this comment.
i don't think we need to have authenticator wrapped around every route, here's the react documentation on how to do this with significantly less repeat code: https://reactrouter.com/start/declarative/routing
There was a problem hiding this comment.
Gonna wait to address this comment for when #94 is merged in
maxn990
left a comment
There was a problem hiding this comment.
a few nits but looking excellent
| import { Module } from '@nestjs/common'; | ||
| import { OwnershipGuard } from './ownership.guard'; | ||
|
|
||
| @Module({ |
There was a problem hiding this comment.
As someone who doesn't know too much about Nest modules, I'd like to know more about how this works/why we need it, especially after I saw that the modules we added this to are not the same ones whose controller methods we added the new decorator to
also, would it make more sense to have this be in a sharedAuth directory, since every other module is essentially the top-level file for the directory it is in?
| confirmDeliveryData.append('dateReceived', formattedDate); | ||
| } else { | ||
| alert('Delivery date is missing or invalid.'); | ||
| return redirect(`/request-form/${pantryId}`); |
There was a problem hiding this comment.
I thought we didn't have IDs in the frontend routes anymore?
| if (error.response?.status === 403) { | ||
| // TODO: For a future ticket, figure out a better method than renavigation on failure (or a better place to check than in the api requests) | ||
| window.location.replace('/unauthorized'); | ||
| if (error.response?.status === 403 && this.navigate) { |
There was a problem hiding this comment.
Maybe in case this.navigate isn't available we should use the window.location.replace as a backup option?
| return true; | ||
| } | ||
|
|
||
| if (!user) { |
There was a problem hiding this comment.
Shouldn't the order of these two checks be swapped?
| @UseGuards(AuthGuard('jwt'), OwnershipGuard) | ||
| @CheckOwnership({ | ||
| idParam: 'orderId', | ||
| resolver: async ({ entityId, services }) => { |
There was a problem hiding this comment.
The length of these decorators could get annoying with a lot of them in each controller file, and it looks like there might be a lot of repetition in the resolver implementations - a lot of them might end up being a series of do a transformation/lambda call, return null if the result is null, repeat. I wonder if there's a way we could create a helper function that abstracts the implementation further to make this more concise. Would be happy to help brainstorm!
ℹ️ Issue
Closes #117
📝 Description
For this PR, we added in user-specific gated authentication to a few endpoints. The initial problem that required this user gate was that we need to restrict certain endpoints that incorporate the id parameter to specific users (for example, a pantry with the id of 3 should not really be able to do anything involving pantries with ids of 2).
To implement this, we are now marking all endpoints with a guard that allows us to pass in a lambda function. This lambda function uses services within the controller to define how we should go about getting the desired id to compare to the id of the currently signed in user (if they are not the same, access should be denied).
We implement a guard with a decorator, just as we did with the role-based authentication. For the decorator, we created 3 things:
moduleReffrom NestJs to get the appropriate service class so we can actually execute the lambda functions. We store them in a cache so we do not need to keep getting them each time,@CheckOwnershipdecorator. It contains the name of the id for us to use in the parameter, as well as the lambda function that will be resolved.For the guard, we use each attribute from the decorator to run the following set of logic steps:
✔️ Verification
For testing, I looked into 2 specific cases. One thing we want to make sure with this PR is that it can handle several services being used in verification (for example with orders, perhaps we are given an order id, and to verify it we need to get the corresponding pantry id, which allows us to get the corresponding pantry which allows us to get the pantry user id which we can check with the signed in user).
For this, I added lambda function guards to an endpoint in the pantries controller (the one called in the request-form page), and one in the orders controller (the one called when we open up the order modal on the admin donation board). I tested both of these pages with and without the corresponding user id being my signed in user. For both of these, I was able to verify that I was kicked out when I wasn't authorized, and allowed in otherwise.
Both these tests ultimately ended up using the
pantry_user_idfield, but just got to it in different ways.🏕️ (Optional) Future Work / Notes
This PR is primarily for infrastructure, and we will later need to adjust all of the endpoints accordingly when we decide who we want to give this endpoint access to.